#ifndef LOG_HPP
#define LOG_HPP

/**
 * \file
 * \ingroup logging
 * \brief Debug message logging.
 */

/**
 * \defgroup logging Logging
 * \brief Logging functions and macros
 *
 * LOG functionality: macros which allow developers to
 * send information to the \c std::clog output stream.
 *
 * All logging messages are disabled by default. To enable selected logging
 * messages, use the cas::LogComponentEnable
 * function or use the SLOG environment variable
 *
 * Use the environment variable SLOG to define a ','-separated list of
 * logging components to enable. For example (using bash syntax),
 * \code
 *   $ SLOG="OlsrAgent" ./cas exec ...
 * \endcode
 * would enable one component at all log levels.
 * \code
 *   $ SLOG="OlsrAgent,Ipv4L3Protocol" ./cas exec ...
 * \endcode
 * would enable two components, at all log levels, etc.
 * \c SLOG="*" will enable all available log components at all levels.
 *
 * To control more selectively the log levels for each component, use
 * this syntax:
 * \code
 *   $ SLOG='Component1=func|warn,Component2=error|debug'
 * \endcode
 * This example would enable the \c func, and \c warn log
 * levels for 'Component1' and the \c error and \c debug log levels
 * for 'Component2'.  The wildcard '*' can be used here as well.  For example
 * \c SLOG='*=level_all|prefix' would enable all log levels and prefix all
 * prints with the component and function names.
 *
 * A note on SLOG_FUNCTION() and SLOG_FUNCTION_NOARGS():
 * generally, use of (at least) SLOG_FUNCTION(this) is preferred,
 * with the any function parameters added:
 * \code
 *   SLOG_FUNCTION(this << arg1 << args);
 * \endcode
 * Use SLOG_FUNCTION_NOARGS() only in static functions with no arguments.
 */
#include "core/log-enabled.hpp"
#include "utils/mock-printer.hpp"
#include "utils/unused.hpp"

/**
 * Definition of empty logging macros and the SLOG_NOOP_INTERNAL macro.
 */
#ifndef SLOG_ENABLE
/*
  Implementation Note:

  std::clog << msg
  This expression is required for forms like SLOG_LOGIC (... << std::endl ...)

  cas::ParameterLogger (std::clog) << msg
  This expression is required for SLOG_FUNCTION (... << vector ...)

  IMO ParameterLogger should work for both cases, but it fails
  with the error:

    note: in expansion of macro SLOG_LOGIC (std::endl...
    error: no match for operator<< (operand types are cas::ParameterLogger
    and <unresolved overloaded function type>)

    note: candidate: template<class T> cas::ParameterLogger& cas::ParameterLogger::operator<<(T)
    note: template argument deduction/substitution failed
    note: couldn't deduce template parameter T

  Note that std::endl is templated manipulator function, which needs the
  target stream to resolve it's own template parameters.  The compiler
  should deduce this from the ParameterLogger::operator<< <T> ()
  implementation, but evidently fails to do so.
*/

/**
 * \ingroup logging
 * Empty logging macro implementation, used when logging is disabled.
 */
#define SLOG_NOOP_INTERNAL(msg) \
    do                          \
        if (false)              \
        {                       \
            std::clog << msg;   \
        }                       \
    while (false)

#define SLOG(level, msg) SLOG_NOOP_INTERNAL(msg)

#define SLOG_FUNCTION_NOARGS()

/**
 * \ingroup logging
 * Empty logging macro implementation, used when logging is disabled.
 */
#define SLOG_NOOP_FUNC_INTERNAL(msg)                \
    do                                              \
        if (false)                                  \
        {                                           \
            cas::ParameterLogger(std::clog) << msg; \
        }                                           \
    while (false)

#define SLOG_FUNCTION(parameters) SLOG_NOOP_FUNC_INTERNAL(parameters)

#define SLOG_UNCOND(msg) SLOG_NOOP_INTERNAL(msg)

#endif /* !SLOG_ENABLE */

namespace cas
{

    /**
     * \ingroup logging
     * Logging severity classes and levels.
     */
    enum LogLevel
    {
        LOG_NONE = 0x00000000, //!< No logging.

        LOG_ERROR = 0x00000001,       //!< Serious error messages only.
        LOG_LEVEL_ERROR = 0x00000001, //!< LOG_ERROR and above.

        LOG_WARN = 0x00000002,       //!< Warning messages.
        LOG_LEVEL_WARN = 0x00000003, //!< LOG_WARN and above.

        LOG_DEBUG = 0x00000004,       //!< Rare ad-hoc debug messages.
        LOG_LEVEL_DEBUG = 0x00000007, //!< LOG_DEBUG and above.

        LOG_INFO = 0x00000008,       //!< Informational messages (e.g., banners).
        LOG_LEVEL_INFO = 0x0000000f, //!< LOG_INFO and above.

        LOG_FUNCTION = 0x00000010,       //!< Function tracing.
        LOG_LEVEL_FUNCTION = 0x0000001f, //!< LOG_FUNCTION and above.

        LOG_LOGIC = 0x00000020,       //!< Control flow tracing within functions.
        LOG_LEVEL_LOGIC = 0x0000003f, //!< LOG_LOGIC and above.

        LOG_ALL = 0x0fffffff,    //!< Print everything.
        LOG_LEVEL_ALL = LOG_ALL, //!< Print everything.

        LOG_PREFIX_FUNC = 0x80000000,  //!< Prefix all trace prints with function.
        LOG_PREFIX_TIME = 0x40000000,  //!< Prefix all trace prints with simulation time.
        LOG_PREFIX_NODE = 0x20000000,  //!< Prefix all trace prints with simulation node.
        LOG_PREFIX_LEVEL = 0x10000000, //!< Prefix all trace prints with log level (severity).
        LOG_PREFIX_ALL = 0xf0000000    //!< All prefixes.
    };

    /**
     * \ingroup logging
     * Enable the logging output associated with that log component.
     *
     * The logging output can be later disabled with a call
     * to cas::LogComponentDisable.
     *
     * Same as running your program with the SLOG environment
     * variable set as SLOG='name=level'.
     *
     * \param [in] name The log component name.
     * \param [in] level The logging level.
     */
    void LogComponentEnable(char const *name, enum LogLevel level);

    /**
     * \ingroup logging
     * Enable the logging output for all registered log components.
     *
     * Same as running your program with the SLOG environment
     * variable set as SLOG='*=level'
     *
     * \param [in] level The logging level.
     */
    void LogComponentEnableAll(enum LogLevel level);

    /**
     * \ingroup logging
     * Disable the logging output associated with that log component.
     *
     * The logging output can be later re-enabled with a call
     * to LogComponentEnable.
     *
     * \param [in] name The log component name.
     * \param [in] level The logging level.
     */
    void LogComponentDisable(char const *name, enum LogLevel level);

    /**
     * \ingroup logging
     * \param [in] level The logging level.
     *
     * Disable all logging for all components.
     */
    void LogComponentDisableAll(enum LogLevel level);

} // namespace cas

/**
 * \ingroup logging
 * Define a Log component with a specific name.
 *
 * This macro should be used at the top of every file in which you want to use
 * the SLOG macro. This macro defines a new LogComponent which can be later
 * selectively enabled or disabled with the cas::LogComponentEnable and
 * cas::LogComponentDisable functions or with the SLOG environment variable.
 *
 * LogComponent names should be simple string tokens, _i.e._,
 * "ArfWifiManager", not "cas::ArfWifiManager".
 *
 * This macro should be placed within namespace cas. If functions outside of
 * namespace cas require access to logging, the preferred solution is to add
 * the following 'using' directive at file scope, outside of namespace cas, and
 * after the inclusion of SLOG_COMPONENT_DEFINE, such as follows:
 * \code
 *   namespace cas {
 *     SLOG_COMPONENT_DEFINE ("XXX");
 *     // Definitions within the cas namespace
 *   }
 *
 *   using cas::g_log;
 *   // Further definitions outside of the ns3 namespace
 * \endcode
 *
 * \param [in] name The log component name.
 */
#define SLOG_COMPONENT_DEFINE(name) \
    static cas::LogComponent g_log = cas::LogComponent(name, __FILE__)

/**
 * \ingroup logging
 * Define a logging component with a mask.
 *
 * See LogComponent().
 *
 * \param [in] name The log component name.
 * \param [in] mask The default mask.
 */
#define SLOG_COMPONENT_DEFINE_MASK(name, mask) \
    static cas::LogComponent g_log = cas::LogComponent(name, __FILE__, mask)

/**
 * \ingroup logging
 * Declare a reference to a Log component.
 *
 * This macro should be used in the declaration of template classes
 * to allow their methods (defined in an header file) to make use of
 * the SLOG_* macros. This macro should be used in the private
 * section to prevent subclasses from using the same log component
 * as the base class.
 */
#define SLOG_TEMPLATE_DECLARE LogComponent &g_log

/**
 * \ingroup logging
 * Initialize a reference to a Log component.
 *
 * This macro should be used in the constructor of template classes
 * to allow their methods (defined in an header file) to make use of
 * the SLOG_* macros.
 *
 * \param [in] name The log component name.
 */
#define SLOG_TEMPLATE_DEFINE(name) g_log(GetLogComponent(name))

/**
 * \ingroup logging
 * Declare and initialize a reference to a LogComponent.
 *
 * This macro should be used in static template methods to allow their
 * methods (defined in an header file) to make use of the SLOG_* macros.
 *
 * \param [in] name The log component name.
 */
#define SLOG_STATIC_TEMPLATE_DEFINE(name) \
    static LogComponent &CAS_UNUSED_GLOBAL(g_log) = GetLogComponent(name)

/**
 * \ingroup logging
 * Use \ref SLOG to output a message of level LOG_ERROR.
 * \param [in] msg The message to log.
 */
#define SLOG_ERROR(msg) SLOG(cas::LOG_ERROR, msg)

/**
 * \ingroup logging
 * Use \ref SLOG to output a message of level LOG_WARN.
 * \param [in] msg The message to log.
 */
#define SLOG_WARN(msg) SLOG(cas::LOG_WARN, msg)

/**
 * \ingroup logging
 * Use \ref SLOG to output a message of level LOG_DEBUG.
 * \param [in] msg The message to log.
 */
#define SLOG_DEBUG(msg) SLOG(cas::LOG_DEBUG, msg)

/**
 * \ingroup logging
 * Use \ref SLOG to output a message of level LOG_INFO.
 * \param [in] msg The message to log.
 */
#define SLOG_INFO(msg) SLOG(cas::LOG_INFO, msg)

/**
 * \ingroup logging
 * Use \ref SLOG to output a message of level LOG_LOGIC
 * \param [in] msg The message to log.
 */
#define SLOG_LOGIC(msg) SLOG(cas::LOG_LOGIC, msg)

namespace cas
{

    /**
     * \ingroup logging
     * Print the list of logging messages available.
     * Same as running your program with the SLOG environment
     * variable set as SLOG=print-list
     */
    void LogComponentPrintList(void);

    /**
     * \ingroup logging
     * Set the TimePrinter function to be used
     * to prepend log messages with the simulation time.
     *
     * The default is DefaultTimePrinter().
     *
     * \param [in] lp The TimePrinter function.
     */
    void LogSetTimePrinter(TimePrinter lp);

    /**
     * \ingroup logging
     * Get the LogTimePrinter function currently in use.
     * \returns The current LogTimePrinter function.
     */
    TimePrinter LogGetTimePrinter(void);

    /**
     * \ingroup logging
     * Set the LogNodePrinter function to be used
     * to prepend log messages with the node id.
     *
     * The default is DefaultNodePrinter().
     *
     * \param [in] np The LogNodePrinter function.
     */
    void LogSetNodePrinter(NodePrinter np);

    /**
     * \ingroup logging
     * Get the LogNodePrinter function currently in use.
     * \returns The current LogNodePrinter function.
     */
    NodePrinter LogGetNodePrinter(void);

    /**
     * \ingroup logging
     * A single log component configuration.
     */
    class LogComponent
    {
    public:
        /**
         * Constructor.
         *
         * \param [in] name The user-visible name for this component.
         * \param [in] file The source code file which defined this LogComponent.
         * \param [in] mask LogLevels blocked for this LogComponent.  Blocking
         *                  a log level helps prevent recursion by logging in
         *                  functions which help implement the logging facility.
         */
        LogComponent(const std::string &name,
                     const std::string &file,
                     const enum LogLevel mask = LOG_NONE);
        /**
         * Check if this LogComponent is enabled for \c level
         *
         * \param [in] level The level to check for.
         * \return \c true if we are enabled at \c level.
         */
        bool IsEnabled(const enum LogLevel level) const;
        /**
         * Check if all levels are disabled.
         *
         * \return \c true if all levels are disabled.
         */
        bool IsNoneEnabled(void) const;
        /**
         * Enable this LogComponent at \c level
         *
         * \param [in] level The LogLevel to enable.
         */
        void Enable(const enum LogLevel level);
        /**
         * Disable logging at \c level for this LogComponent.
         *
         * \param [in] level The LogLevel to disable.
         */
        void Disable(const enum LogLevel level);
        /**
         * Get the name of this LogComponent.
         *
         * \return The name of this LogComponent.
         */
        char const *Name(void) const;
        /**
         * Get the compilation unit defining this LogComponent.
         * \returns The file name.
         */
        std::string File(void) const;
        /**
         * Get the string label for the given LogLevel.
         *
         * \param [in] level The LogLevel to get the label for.
         * \return The string label for \c level.
         */
        static std::string GetLevelLabel(const enum LogLevel level);
        /**
         * Prevent the enabling of a specific LogLevel.
         *
         * \param [in] level The LogLevel to block.
         */
        void SetMask(const enum LogLevel level);

        /**
         * LogComponent name map.
         *
         * \internal
         * This should really be considered an internal API.
         * It is exposed here to allow print-introspected-doxygen.cc
         * to generate a list of all LogComponents.
         */
        typedef std::unordered_map<std::string, LogComponent *> ComponentList;

        /**
         * Get the list of LogComponnents.
         *
         * \internal
         * This should really be considered an internal API.
         * It is exposed here to allow print-introspected-doxygen.cc
         * to generate a list of all LogComponents.
         *
         * \returns The list of LogComponents.
         */
        static ComponentList *GetComponentList(void);

    private:
        /**
         * Parse the `NS_LOG` environment variable for options relating to this
         * LogComponent.
         */
        void EnvVarCheck(void);

        int32_t m_levels;   //!< Enabled LogLevels.
        int32_t m_mask;     //!< Blocked LogLevels.
        std::string m_name; //!< LogComponent name.
        std::string m_file; //!< File defining this LogComponent.

    }; // class LogComponent

    /**
     * \ingroup logging
     * Get the LogComponent registered with the given name.
     *
     * \param [in] name The name of the LogComponent.
     * \return a reference to the requested LogComponent
     */
    LogComponent &GetLogComponent(const std::string name);

    /**
     * \ingroup logging
     * Insert `, ` when streaming function arguments.
     */
    class ParameterLogger
    {
        bool m_first;       //!< First argument flag, doesn't get `, `.
        std::ostream &m_os; //!< Underlying output stream.

    public:
        /**
         * Constructor.
         *
         * \param [in] os Underlying output stream.
         */
        ParameterLogger(std::ostream &os);

        /**
         * Write a function parameter on the output stream,
         * separating parameters after the first by `,` strings.
         *
         * \param [in] param The function parameter.
         * \return This ParameterLogger, so it's chainable.
         */
        template <typename T>
        ParameterLogger &operator<<(T param);

        /**
         * Overload for vectors, to print each element.
         *
         * \param [in] vector The vector of parameters
         * \return This ParameterLogger, so it's chainable.
         */
        template <typename T>
        ParameterLogger &operator<<(std::vector<T> vector);
    };

    template <typename T>
    ParameterLogger &
    ParameterLogger::operator<<(T param)
    {
        if (m_first)
        {
            m_os << param;
            m_first = false;
        }
        else
        {
            m_os << ", " << param;
        }
        return *this;
    }

    template <typename T>
    ParameterLogger &
    ParameterLogger::operator<<(std::vector<T> vector)
    {
        for (const auto &i : vector)
        {
            *this << i;
        }
        return *this;
    }

    /**
     * Specialization for strings.
     * \param [in] param The function parameter.
     * \return This ParameterLogger, so it's chainable.
     */
    template <>
    ParameterLogger &
    ParameterLogger::operator<<<std::string>(const std::string param);

    /**
     * Specialization for C-strings.
     * \param [in] param The function parameter.
     * \return This ParameterLogger, so it's chainable.
     */
    template <>
    ParameterLogger &
    ParameterLogger::operator<<<const char *>(const char *param);

    /**
     * Specialization for int8_t.
     * \param [in] param The function parameter.
     * \return This ParameterLogger, so it's chainable.
     */
    template <>
    ParameterLogger &
    ParameterLogger::operator<<<int8_t>(int8_t param);

    /**
     * Specialization for uint8_t.
     * \param [in] param The function parameter.
     * \return This ParameterLogger, so it's chainable.
     */
    template <>
    ParameterLogger &
    ParameterLogger::operator<<<uint8_t>(uint8_t param);

} // namespace cas

#endif /* LOG_HPP */
